from flask import *
from flask_login import *
from models import *
from werkzeug.utils import secure_filename
import shutil
import zipfile
import io
from PIL import Image
import os

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
app.config['SECRET_KEY'] = '555'
db.init_app(app)

# 文件系统默认根目录路径
directory = f'../ali_web_floder'
os.makedirs(directory, exist_ok=True)  # 确保文件夹存在
# 上传文件的文件夹
UPLOAD_FOLDER = 'static/uploads/'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
login_manager = LoginManager()
login_manager.init_app(app)


@login_manager.user_loader
def load_user(user_id):
    """根据用户ID加载用户信息"""
    return User.query.get(int(user_id))


# --------------------------------------------------
def save_attachment(attachment, post_id):
    # 获取上传文件夹路径
    post_folder_path = os.path.join(app.config['UPLOAD_FOLDER'], str(post_id))
    os.makedirs(post_folder_path, exist_ok=True)  # 确保文件夹存在

    # 安全获取文件名
    filename = secure_filename(attachment.filename)
    file_path = os.path.join(post_folder_path, filename)

    # 检查文件名是否已存在，循环检测
    count = 1
    while os.path.exists(file_path):
        # 在文件名前添加计数
        base, ext = os.path.splitext(filename)
        filename = f"{count + 1}-{base}{ext}"
        file_path = os.path.join(post_folder_path, filename)
        count += 1

    # 保存附件到新文件名
    attachment.save(file_path)
    return filename  # 返回新文件名


# --------------------------------------------------
@app.route('/')
def index():
    """首页，显示10条帖子"""
    page = request.args.get('page', 1, type=int)  # 获取当前页码，默认是1

    # 先对帖子按照创建时间进行降序排序，然后进行分页
    posts = Post.query.order_by(Post.created_at.desc()).paginate(page=page, per_page=10)

    # 检查页码是否超出范围
    if page > posts.pages or page < 1:
        flash('请求的页码超出最大页数，请重新选择！', 'warning')
        return redirect(url_for('index', page=1))  # 重定向到第一页

    return render_template('index.html', posts=posts.items, page=page, has_next=posts.has_next)


# --------------------------------------------------

@app.before_request
def create_admin():
    """检查并创建管理员账户"""
    admin = User.query.filter_by(username='ali').first()
    if not admin:
        admin = User(username='ali', password='1111aaaa', is_admin=True)
        db.session.add(admin)
        db.session.commit()


# --------------------------------------------------

@app.route('/register', methods=['GET', 'POST'])
def register():
    """用户注册功能"""
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = User(username=username, password=password)
        print(f"注册用户：{username}, 密码：{password}")
        db.session.add(user)
        db.session.commit()
        flash('注册成功，请登录！')
        return redirect(url_for('login'))
    return render_template('register.html')


# --------------------------------------------------

@app.route('/login', methods=['GET', 'POST'])
def login():
    """用户登录功能"""
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = User.query.filter_by(username=username).first()

        if user is None:
            flash('账户不存在，请检查用户名。', 'error')
        elif user.password != password:
            flash('密码错误，请重试。', 'error')
        else:
            login_user(user)
            # flash('登录成功，欢迎回来！', 'success')
            return redirect(url_for('index'))  # 登录成功后回到主页
    return render_template('login.html')


# --------------------------------------------------

@app.route('/delete_post/<int:post_id>', methods=['POST'])
@login_required
def delete_post(post_id):
    """删除指定ID的帖子及其相关评论和附件"""
    post = Post.query.get_or_404(post_id)

    # 检查当前用户是否是帖子作者或管理员
    if post.author != current_user and not current_user.is_admin:
        flash('您没有权限删除该帖子！')
        return redirect(url_for('index'))

    # 删除与帖子相关的所有评论
    for comment in post.comments:
        # 删除评论的附件文件
        if comment.attachment:
            comment_folder_path = os.path.join(app.config['UPLOAD_FOLDER'], str(post.id), current_user.username)
            attachment_path = os.path.join(comment_folder_path, comment.attachment)
            if os.path.exists(attachment_path):
                os.remove(attachment_path)  # 删除附件文件
        db.session.delete(comment)

    db.session.delete(post)
    db.session.commit()

    # 删除帖子的文件夹及其内容
    post_folder_path = os.path.join(app.config['UPLOAD_FOLDER'], str(post.id))
    if os.path.exists(post_folder_path):
        shutil.rmtree(post_folder_path)  # 使用shutil.rmtree递归删除文件夹及其内容

    flash('帖子已删除！')
    return redirect(url_for('dashboard'))


# --------------------------------------------------
@app.route('/delete_comment/<int:comment_id>', methods=['POST'])
@login_required
def delete_comment(comment_id):
    """删除指定ID的评论及其附件"""
    comment = Comment.query.get_or_404(comment_id)

    # 检查用户权限
    if comment.post.author != current_user and not current_user.is_admin:
        flash('您没有权限删除该评论！')
        return redirect(url_for('post_detail', post_id=comment.post_id))

    # 删除评论的附件
    if comment.attachment:
        attachment_path = os.path.join(app.config['UPLOAD_FOLDER'], str(comment.post.id), comment.attachment)
        if os.path.exists(attachment_path):
            os.remove(attachment_path)  # 删除附件文件

    db.session.delete(comment)
    db.session.commit()

    flash('评论已删除！')
    return redirect(url_for('post_detail', post_id=comment.post_id))


# --------------------------------------------------
# 创建缩略图的函数
def create_thumbnail(image_path, thumbnail_path, size=(50, 50)):
    with Image.open(image_path) as img:
        img.thumbnail(size)  # 根据指定尺寸创建缩略图
        img.save(thumbnail_path)  # 保存缩略图

# --------------------------------------------------
# 个人后台
@app.route('/dashboard', methods=['GET', 'POST'])
@login_required
def dashboard():
    """用户后台，允许用户发布帖子和查看帖子"""
    if request.method == 'POST':
        title = request.form['title']
        content = request.form['content']
        attachment = request.files.get('attachment')

        post = Post(title=title, content=content, author=current_user)

        db.session.add(post)  # 将帖子添加到会话中以获取ID
        db.session.commit()  # 提交以确保获取到post.id

        if attachment:
            # 创建文件夹，文件夹名称为帖子ID
            post_folder_path = os.path.join(app.config['UPLOAD_FOLDER'], str(post.id))
            os.makedirs(post_folder_path, exist_ok=True)  # 确保文件夹存在

            filename = secure_filename(attachment.filename)
            attachment_path = os.path.join(post_folder_path, filename)
            attachment.save(attachment_path)  # 保存附件到新建的文件夹
            post.attachment = filename  # 存储附件文件名

            # # 如果附件是图片，则生成缩略图
            if filename.endswith(('.jpg', '.jpeg', '.png', '.gif', '.bmp')):
                thumbnail_filename = 'thumb_' + filename
                thumbnail_path = os.path.join(post_folder_path, thumbnail_filename)
                create_thumbnail(attachment_path, thumbnail_path)  # 生成缩略图
                post.thumbnail = thumbnail_filename  # 存储缩略图文件名

            db.session.commit()  # 提交以更新帖子记录

        flash('帖子已发布！')
        return redirect(url_for('dashboard'))
    return render_template('dashboard.html')


# --------------------------------------------------

@app.route('/post/<int:post_id>', methods=['GET', 'POST'])
def post_detail(post_id):
    post = Post.query.get_or_404(post_id)
    if request.method == 'POST':
        comment_content = request.form['content']
        attachment = request.files.get('attachment')

        # 创建评论实例
        new_comment = Comment(content=comment_content, post=post, user=current_user)  # 确保用户被添加

        # 保存评论的附件（如果有）
        if attachment:
            # 安全获取文件名
            attachment_filename = secure_filename(attachment.filename)
            # 确定评论文件夹路径
            comment_folder_path = os.path.join(app.config['UPLOAD_FOLDER'], str(post.id))
            os.makedirs(comment_folder_path, exist_ok=True)  # 确保文件夹存在

            # 循环检查文件名是否存在
            file_path = os.path.join(comment_folder_path, attachment_filename)
            count = 1
            while os.path.exists(file_path):
                base, ext = os.path.splitext(attachment_filename)
                attachment_filename = f"{count + 1}-{base}{ext}"
                file_path = os.path.join(comment_folder_path, attachment_filename)
                count += 1

            # 保存附件到新文件名
            attachment.save(file_path)
            new_comment.attachment = attachment_filename  # 存储附件文件名

            # 生成缩略图
            thumbnail_filename = f"thumbnail_{attachment_filename}"
            thumbnail_path = os.path.join(comment_folder_path, thumbnail_filename)
            create_thumbnail(file_path, thumbnail_path)

        db.session.add(new_comment)
        db.session.commit()
        flash('评论发布成功！')
        return redirect(url_for('post_detail', post_id=post_id))

    comments = post.comments  # 获取评论
    return render_template('post_detail.html', post=post, comments=comments)


# --------------------------------------------------

@app.route('/logout')
@login_required
def logout():
    """用户登出功能"""
    logout_user()
    return redirect(url_for('index'))


# --------------------------------------------------

@app.route('/user/<int:user_id>')
def user_posts(user_id):
    """显示特定用户的所有帖子"""
    user = User.query.get_or_404(user_id)
    posts = user.posts  # 获取该用户的所有帖子
    return render_template('user_posts.html', user=user, posts=posts)


# --------------------------------------------------

# led路由部分，显示LED控制页面
# --------------------------------------------------


# 存储每个MAC地址对应的按钮状态
led_states = {}


# 处理 /LED/<string:mac> 路由的 GET 请求
@app.route('/LED/<string:mac>', methods=['GET'])
def led_form(mac):
    if mac not in led_states:
        led_states[mac] = 0
    current_state = led_states[mac]
    button_text = "关灯" if current_state == 1 else "开灯"
    return render_template('LED.html', mac=mac, button_text=button_text, current_state=current_state)


# 处理 /LEDcontrol/<string:mac> 路由的 GET 和 POST 请求
@app.route('/LEDcontrol/<string:mac>', methods=['GET', 'POST'])
def led_control(mac):
    if mac not in led_states:
        led_states[mac] = 0
    if request.method == 'POST':
        led_states[mac] = int(request.form.get('buttonState'))
        return redirect(url_for('led_form', mac=mac))

    elif request.method == 'GET':
        return f'buttonState:{led_states[mac]}'


# --------------------------------------------------
# mqtt路由
@app.route('/mqtt', methods=['GET'])
def mqtt():
    return render_template('mqtt.html')


# --------------------------------------------------

# 文件管理系统
# ----------------------------------------------------------------------------------------------------------------------------------------------------

@app.route('/files/')
def files_view():
    """返回根目录文件列表"""
    return list_files(directory)


@app.route('/files/<path:foldername>')
def files(foldername):
    """列出指定文件夹中的内容"""
    full_path = os.path.join(directory, foldername)

    if not os.path.isdir(full_path):
        abort(404)  # 如果路径不是文件夹，则返回404错误

    return list_files(full_path)


def list_files(path):
    """列出指定路径下的所有文件和文件夹"""
    entries = os.listdir(path)  # 获取目录中的所有文件和文件夹
    file_links = []

    for entry in entries:
        full_path = os.path.join(path, entry)  # 构造完整路径
        if os.path.isdir(full_path):
            # 创建文件夹链接
            link = f'<li><a href="/files/{os.path.relpath(full_path, directory)}">{entry}/</a>'
            link += f' <a href="/download/{os.path.relpath(full_path, directory)}" class="btn-download">下载</a>'
            # 如果用户已登录且是管理员，则添加删除按钮
            if current_user.is_authenticated and current_user.is_admin:
                link += f' <a href="/delete/{os.path.relpath(full_path, directory)}" class="btn-delete">删除</a>'
            link += '</li>'  # 关闭文件夹的li标签
        else:
            # 创建文件链接
            link = f'<li><a href="/download/{os.path.relpath(full_path, directory)}">{entry}</a>'
            # 如果用户已登录且是管理员，则添加删除按钮
            if current_user.is_authenticated and current_user.is_admin:
                link += f' <a href="/delete/{os.path.relpath(full_path, directory)}" class="btn-delete">删除</a>'
            link += '</li>'  # 关闭文件的li标签

        file_links.append(link)  # 添加链接到文件列表

    # 获取上一级目录路径
    parent_path = os.path.dirname(path)

    # 渲染文件视图模板，传递路径、文件列表和上一级目录
    return render_template('file_view.html', path=path, file_links=''.join(file_links),
                           current_path=os.path.relpath(path, directory),
                           parent_path=os.path.relpath(parent_path, directory))


@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    """处理文件上传"""
    current_path = request.args.get('current_path', '')  # 获取当前路径

    if request.method == 'POST':
        # 处理上传的文件
        uploaded_files = request.files.getlist('files[]')
        for uploaded_file in uploaded_files:
            if uploaded_file.filename != '':
                # 构造保存路径
                save_path = os.path.join(directory, current_path, uploaded_file.filename)
                os.makedirs(os.path.dirname(save_path), exist_ok=True)  # 确保路径存在
                uploaded_file.save(save_path)  # 保存文件
        return list_files(os.path.join(directory, current_path))  # 返回当前路径的文件列表

    return render_template('upload.html')


@app.route('/download/<path:filepath>')
def download_file(filepath):
    """下载指定文件或文件夹（打包为ZIP文件）"""
    full_path = os.path.join(directory, filepath)

    if os.path.isdir(full_path):
        return send_zip(full_path)  # 如果是文件夹，发送ZIP文件

    if not os.path.exists(full_path):
        abort(404)  # 如果文件不存在，返回404错误

    return send_file(full_path, as_attachment=True)  # 发送文件以进行下载


@app.route('/delete/<path:filepath>')
def delete_file(filepath):
    """删除指定文件或文件夹"""
    full_path = os.path.join(directory, filepath)  # 构造完整路径
    current_directory = os.path.dirname(filepath)  # 获取当前目录

    try:
        if os.path.exists(full_path):
            if os.path.isdir(full_path):
                shutil.rmtree(full_path)  # 删除非空文件夹
                flash(f"已成功删除{filepath}")
            else:
                os.remove(full_path)  # 删除文件
        else:
            return f"文件或文件夹不存在: {full_path}", 404  # 返回404错误
    except Exception as e:
        return str(e), 500  # 返回错误信息

    # 返回到原始目录
    return redirect(url_for('files', foldername=current_directory))


def send_zip(folder_path):
    """将文件夹打包为ZIP文件并发送"""
    zip_buffer = io.BytesIO()  # 创建内存中的ZIP文件
    folder_name = os.path.basename(os.path.normpath(folder_path))  # 获取文件夹名称
    with zipfile.ZipFile(zip_buffer, 'w') as zip_file:
        # 遍历文件夹中的所有文件并添加到ZIP文件中
        for root, _, files in os.walk(folder_path):
            for file in files:
                zip_file.write(os.path.join(root, file),
                               os.path.relpath(os.path.join(root, file), folder_path))
    zip_buffer.seek(0)  # 将指针移回到缓冲区的开头
    return send_file(zip_buffer, as_attachment=True, download_name=f'{folder_name}.zip',
                     mimetype='application/zip')  # 发送ZIP文件


# --------------------------------------------------

if __name__ == '__main__':
    host = "0.0.0.0"
    port = 8052
    with app.app_context():
        db.create_all()  # 创建所有数据库表
    app.run(host=host, port=port, threaded=True, debug=True)
